﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ImageK.IO;
using ImageK.Macros;
using ImageK.Measure;
using ImageK.Util;

namespace ImageK.Gui
{
    /// <summary>
    /// A frame for displaying images.
    /// </summary>
    public partial class ImageWindow : Form
    {
        public const int MIN_WIDTH = 128;
        public const int MIN_HEIGHT = 32;
        public const int HGAP = 5;
        public const int VGAP = 5;
        public const string LOC_KEY = "image.loc";
	
        protected ImagePlus imp;
        protected ImageK ij;
        protected ImageCanvas ic;

        private double initialMagnification = 1;
        private int newWidth, newHeight;
        protected bool closed;
        private bool newCanvas;
        private bool unzoomWhenMinimizing = true;
        public Rectangle maxWindowBounds; // largest possible window on this screen
        public Rectangle maxBounds; // Size of this window after it is maximized
        public long setMaxBoundsTime;
        private int sliderHeight;

        private const int XINC = 12;
        private const int YINC = 16;

        private double SCALE = Prefs.getGuiScale();
        private int TEXT_GAP = 11;

        private static int xbase = -1;
        private static int ybase;
        private static int xloc;
        private static int yloc;
        private static int count;

        private static bool centerOnScreen;
        private static Point nextLocation;
        public static long setMenuBarTime;
        private int textGap;

        private Point initialLoc;
        private int screenHeight, screenWidth;


        /** This variable is set false if the user presses the escape key or closes the window. */
        public bool running;

        /** This variable is set false if the user clicks in this
            window, presses the escape key, or closes the window. */
        public bool running2;


        protected Panel subTitlePanel;


        public ImageWindow()
        {
            InitializeComponent();
            this.Activated += windowActivated;
        }

        public ImageWindow(ImagePlus imp):this(imp, null)
        {
        }

        public ImageWindow(ImagePlus imp, ImageCanvas ic):this()
        {
            //todo:
            this.Text = imp.getTitle();
            if (SCALE > 1.0)
            {
                TEXT_GAP = (int)(TEXT_GAP * SCALE);
                textGap = centerOnScreen ? 0 : TEXT_GAP;
            }

            if (Prefs.blackCanvas)
            {
                this.ForeColor = Color.White;
                this.BackColor = Color.Black;
            }
            else
            {
                this.ForeColor = Color.Black;
            }

            this.BackColor = Color.White;

            CreateSubTitlePanel();

            bool openAsHyperStack = imp.getOpenAsHyperStack();
            ij = IJ.GetInstance();
            this.imp = imp;
            if (ic == null)
            {
                ic = (this is PlotWindow) ? new PlotCanvas(imp) : new ImageCanvas(imp);
                newCanvas = true;
            }
            this.ic = ic;
            ImageWindow previousWindow = imp.getWindow();

            //添加UI监听事件，添加控件
            ic.Location = new Point(5, subTitlePanel.Height);
            ic.Draw();
            add(ic);


            if (!(this is HistogramWindow&& IJ.isMacro() && Interpreter.isBatchMode())) {
                WindowManager.addWindow(this);
                imp.setWindow(this);
            }
            //
            if (previousWindow != null)
            {
                if (newCanvas)
                    setLocationAndSize(false);
                else
                    ic.update(previousWindow.getCanvas());
            }
            else
            {
                setLocationAndSize(false);
                Show();
            }

        }

        protected override void OnActivated(EventArgs e)
        {
            //todo:
            if (IJ.debugMode) IJ.log("windowActivated: " + imp.getTitle());
            // if (IJ.isMacOSX())
            //     setImageJMenuBar(this);
            if (imp == null)
                return;
            ImageK ij = IJ.GetInstance();
            if (ij != null && !closed && !ij.quitting() && !Interpreter.isBatchMode())
                WindowManager.setCurrentWindow(this);
            //todo:
            // Roi roi = imp.getRoi();
            // if (roi != null && (roi instanceof PointRoi))
            //     PointToolOptions.update();
            // if (imp.isComposite())
            //     Channels.updateChannels();
            imp.setActivated(); // notify ImagePlus that image has been activated
            base.OnActivated(e);
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            if (closed)
                return;
            // if (ij != null)
            // {
            //     WindowManager.setCurrentWindow(this);
            //     IJ.doCommand("Close");
            // }
            // else
            // {
            //     dispose();
            //     WindowManager.removeWindow(this);
            // }

            base.OnFormClosing(e);
        }


        private void CreateSubTitlePanel()
        {
            subTitlePanel = new Panel();
            subTitlePanel.Location = new Point(0, 0);
            subTitlePanel.Width = this.Width;
            subTitlePanel.Height = 20;
            subTitlePanel.Paint += SubTitlePanelOnPaint;
            add(subTitlePanel);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            subTitlePanel.Invalidate();
            base.OnPaint(e);
        }

        private void SubTitlePanelOnPaint(object? sender, PaintEventArgs e)
        {
            drawInfo(e.Graphics);
        }

        public void drawInfo(Graphics g)
        {
            if (imp == null)
                return;

            Font font = new Font("SansSerif", (int)(10 * SCALE), FontStyle.Regular);
            g.DrawString(createSubtitle(), font, new SolidBrush(Color.Black),new PointF(5, 0));


            //todo:
            // if (imp == null)
            //     return;
            // if (textGap != 0)
            // {
            //     Insets insets = super.getInsets();
            //     Color savec = null;
            //     if (imp.isComposite())
            //     {
            //         CompositeImage ci = (CompositeImage)imp;
            //         if (ci.getMode() == IJ.COMPOSITE)
            //         {
            //             savec = g.getColor();
            //             Color c = ci.getChannelColor();
            //             if (Color.green.equals(c))
            //                 c = new Color(0, 180, 0);
            //             g.setColor(c);
            //         }
            //     }
            //     Java2.setAntialiasedText(g, true);
            //     if (SCALE > 1.0)
            //     {
            //         Font font = new Font("SansSerif", Font.PLAIN, (int)(12 * SCALE));
            //         g.setFont(font);
            //     }
            //     g.drawString(createSubtitle(), insets.left + 5, insets.top + TEXT_GAP);
            //     if (savec != null)
            //         g.setColor(savec);
            // }
        }

        public virtual string createSubtitle()
        {
            string s = "";
            if (imp == null)
                return s;
            int stackSize = imp.getStackSize();
            if (stackSize > 1)
            {
                ImageStack stack = imp.getStack();
                int currentSlice = imp.getCurrentSlice();
                s += currentSlice + "/" + stackSize;
                string label = stack.getShortSliceLabel(currentSlice);
                if (label != null && label.Length > 0)
                {
                    if (imp.isHyperStack()) label = label.Replace(';', ' ');
                    s += " (" + label + ")";
                }
                if ((this is StackWindow) && running2) {
                    return s;
                }
                s += "; ";
            }
            else
            {
                string label = imp.getProp("Slice_Label");
                if (label == null && imp.hasImageStack())
                    label = imp.getStack().getSliceLabel(1);
                if (label != null && label.Length > 0)
                {
                    int newline = label.IndexOf('\n');
                    if (newline > 0)
                        label = label.Substring(0, newline);
                    int len = label.Length;
                    if (len > 4 && label[len - 4] == '.' && !Char.IsDigit(label[len - 1]))
                        label = label.Substring(0, len - 4);
                    if (label.Length > 60)
                        label = label.Substring(0, 60) + "...";
                    s = "\"" + label + "\"; ";
                }
            }
            int type = imp.getType();
            Calibration cal = imp.getCalibration();
            if (cal.scaled())
            {
                bool unitsMatch = cal.getXUnit().Equals(cal.getYUnit());
                double cwidth = imp.getWidth() * cal.pixelWidth;
                double cheight = imp.getHeight() * cal.pixelHeight;
                int digits = Tools.getDecimalPlaces(cwidth, cheight);
                if (digits > 2) digits = 2;
                if (unitsMatch)
                {
                    s += IJ.d2s(cwidth, digits) + "x" + IJ.d2s(cheight, digits)
                        + " " + cal.getUnits() + " (" + imp.getWidth() + "x" + imp.getHeight() + "); ";
                }
                else
                {
                    s += d2s(cwidth) + " " + cal.getXUnit() + " x "
                        + d2s(cheight) + " " + cal.getYUnit()
                        + " (" + imp.getWidth() + "x" + imp.getHeight() + "); ";
                }
            }
            else
                s += imp.getWidth() + "x" + imp.getHeight() + " pixels; ";
            switch (type)
            {
                case ImagePlus.GRAY8:
                case ImagePlus.COLOR_256:
                    s += "8-bit";
                    break;
                case ImagePlus.GRAY16:
                    s += "16-bit";
                    break;
                case ImagePlus.GRAY32:
                    s += "32-bit";
                    break;
                case ImagePlus.COLOR_RGB:
                    s += imp.isRGB() ? "RGB" : "32-bit (int)";
                    break;
            }
            if (imp.isInvertedLut())
                s += " (inverting LUT)";
            return s + "; " + getImageSize(imp);
        }

        private string d2s(double n)
        {
            int digits = Tools.getDecimalPlaces(n);
            if (digits > 2) digits = 2;
            return IJ.d2s(n, digits);
        }

        /// <summary>
        /// 设置窗体的位置和大小
        /// </summary>
        /// <param name="updating"></param>
        private void setLocationAndSize(bool updating)
        {
            if (imp == null)
                return;
            int width = imp.getWidth();
            int height = imp.getHeight();

            //todo:
            this.Width = width + 30;
            if (imp.getStackSize() > 1)
            {
                this.Height = height + 140;
            }
            else
            {
                this.Height = height + 120;
            }

            // load prefernces file location
            // Point loc = Prefs.getLocation(LOC_KEY);
            // Rectangle bounds = null;
            // if (loc != null)
            // {
            //     bounds = GUI.getMaxWindowBounds(loc);
            //     if (bounds != null && (loc.x > bounds.x + bounds.width / 3 || loc.y > bounds.y + bounds.height / 3)
            //     && (loc.x + width > bounds.x + bounds.width || loc.y + height > bounds.y + bounds.height))
            //     {
            //         loc = null;
            //         bounds = null;
            //     }
            // }


            // if loc not valid, use screen bounds of visible window (this) or of main window (ij) if not visible yet (updating == false)
            // Rectangle maxWindow = bounds != null ? bounds : GUI.getMaxWindowBounds(updating ? this : ij);
            //
            // if (WindowManager.getWindowCount() <= 1)
            //     xbase = -1;
            // if (width > maxWindow.width / 2 && xbase > maxWindow.x + 5 + XINC * 6)
            //     xbase = -1;
            // if (xbase == -1)
            // {
            //     count = 0;
            //     if (loc != null)
            //     {
            //         xbase = loc.x;
            //         ybase = loc.y;
            //     }
            //     else if (ij != null)
            //     {
            //         Rectangle ijBounds = ij.getBounds();
            //         if (ijBounds.y - maxWindow.x < maxWindow.height / 8)
            //         {
            //             xbase = ijBounds.x;
            //             if (xbase + width > maxWindow.x + maxWindow.width)
            //             {
            //                 xbase = maxWindow.x + maxWindow.width - width - 10;
            //                 if (xbase < maxWindow.x)
            //                     xbase = maxWindow.x + 5; ;
            //             }
            //             ybase = ijBounds.y + ijBounds.height + 5;
            //         }
            //         else
            //         {
            //             xbase = maxWindow.x + (maxWindow.width - width) / 2;
            //             ybase = maxWindow.y + (maxWindow.height - height) / 4;
            //         }
            //     }
            //     else
            //     {
            //         xbase = maxWindow.x + (maxWindow.width - width) / 2;
            //         ybase = maxWindow.y + (maxWindow.height - height) / 4;
            //     }
            //     xbase = Math.max(xbase, maxWindow.x);
            //     ybase = Math.max(ybase, maxWindow.y);
            //     //if (IJ.debugMode) IJ.log("ImageWindow.xbase: "+xbase);
            //     xloc = xbase;
            //     yloc = ybase;
            // }
            // int x = xloc;
            // int y = yloc;
            // xloc += XINC;
            // yloc += YINC;
            // count++;
            // if (count % 6 == 0)
            // {
            //     xloc = xbase;
            //     yloc = ybase;
            // }
            //
            // screenHeight = maxWindow.y + maxWindow.height - sliderHeight;
            // screenWidth = maxWindow.x + maxWindow.width;
            // double mag = 1;
            // if (!(this instanceof PlotWindow)) { // unless a plot (always at 100%), zoom out to show all of image
            //     while (xbase + width * mag > screenWidth || ybase + height * mag >= screenHeight)
            //     {
            //         double mag2 = ImageCanvas.getLowerZoomLevel(mag);
            //         if (mag2 == mag) break;
            //         mag = mag2;
            //     }
            // }
            //
            // if (mag < 1.0)
            // {
            //     initialMagnification = mag;
            //     ic.setSize((int)(width * mag), (int)(height * mag));
            // }
            // ic.setMagnification(mag);
            // if (y + height * mag > screenHeight)
            //     y = ybase;
            // if (Prefs.open100Percent && ic.getMagnification() < 1.0)
            // {
            //     while (ic.getMagnification() < 1.0)
            //         ic.zoomIn(0, 0);
            //     setSize(Math.min(width, screenWidth - x), Math.min(height, screenHeight - y));
            //     validate();
            // }
            // else
            //     pack();
            // if (!updating)
            // {
            //     setLocation(x, y);
            //     initialLoc = new Point(x, y);
            // }
        }

        /// <summary>
        /// Causes this Window to be sized to fit the preferred size and layouts of its subcomponents. The resulting width and height of the window are
        /// automatically enlarged if either of dimensions is less than the minimum size as specified by the previous call to the setMinimumSize method.
        /// If the window and/or its owner are not displayable yet, both of them are made displayable before calculating the preferred size.The Window is
        /// validated after its size is being calculated.
        /// </summary>
        protected virtual void pack()
        {

        }

        public void updateImage(ImagePlus imp)
        {
            if (imp != this.imp)
                throw new ArgumentException("imp!=this.imp");
            this.imp = imp;
            ic.updateImage(imp);
            setLocationAndSize(true);
            if (this is StackWindow) {
                StackWindow sw = (StackWindow)this;
                int stackSize = imp.getStackSize();
                int nScrollbars = sw.getNScrollbars();
                if (stackSize == 1 && nScrollbars > 0)
                    sw.removeScrollbars();
                else if (stackSize > 1 && nScrollbars == 0)
                    sw.addScrollbars(imp);
            }
            // pack();
            // repaint();
            imp.repaintWindow();

            maxBounds = getMaximumBounds();
            setMaximizedBounds(maxBounds);
            setMaxBoundsTime = DateTime.Now.Ticks;
        }

        public ImageCanvas getCanvas()
        {
            return ic;
        }

        public Control add(Control comp)
        {
            LayoutPanel.Controls.Add(comp);
            maxBounds = getMaximumBounds();
            setMaximizedBounds(maxBounds);
            setMaxBoundsTime = DateTime.Now.Ticks;
            return comp;
        }


        public Rectangle getMaximumBounds()
        {
            //todo:
            return new Rectangle(0, 0, 512, 512);
            // Rectangle maxWindow = GUI.getMaxWindowBounds(this);
            // if (imp == null)
            //     return maxWindow;
            // double width = imp.getWidth();
            // double height = imp.getHeight();
            // double iAspectRatio = width / height;
            // maxWindowBounds = maxWindow;
            // if (iAspectRatio / ((double)maxWindow.width / maxWindow.height) > 0.75)
            // {
            //     maxWindow.y += 22;  // uncover ImageJ menu bar
            //     maxWindow.height -= 22;
            // }
            // Dimension extraSize = getExtraSize();
            // double maxWidth = maxWindow.width - extraSize.width;
            // double maxHeight = maxWindow.height - extraSize.height;
            // double mAspectRatio = maxWidth / maxHeight;
            // int wWidth, wHeight;
            // double mag;
            // if (iAspectRatio >= mAspectRatio)
            // {
            //     mag = maxWidth / width;
            //     wWidth = maxWindow.width;
            //     wHeight = (int)(height * mag + extraSize.height);
            // }
            // else
            // {
            //     mag = maxHeight / height;
            //     wHeight = maxWindow.height;
            //     wWidth = (int)(width * mag + extraSize.width);
            // }
            // int xloc = (int)(maxWidth - wWidth) / 2;
            // if (xloc < maxWindow.x) xloc = maxWindow.x;
            // wWidth = Math.min(wWidth, maxWindow.x - xloc + maxWindow.width);
            // wHeight = Math.min(wHeight, maxWindow.height);
            // return new Rectangle(xloc, maxWindow.y, wWidth, wHeight);
        }

        public void setMaximizedBounds(Rectangle bounds)
        {
            //todo:

            // synchronized(getObjectLock()) {
            //     this.maximizedBounds = bounds;
            // }
            // FramePeer peer = (FramePeer)this.peer;
            // if (peer != null)
            // {
            //     peer.setMaximizedBounds(bounds);
            // }
        }

        public static string getImageSize(ImagePlus imp)
        {
            if (imp == null)
                return null;
            double size = imp.getSizeInBytes() / 1024.0;
            string s2 = null, s3 = null;
            if (size < 1024.0)
            { s2 = IJ.d2s(size, 0); s3 = "K"; }
            else if (size < 10000.0)
            { s2 = IJ.d2s(size / 1024.0, 1); s3 = "MB"; }
            else if (size < 1048576.0)
            { s2 = IJ.d2s(Math.Round(size / 1024.0), 0); s3 = "MB"; }
            else
            { s2 = IJ.d2s(size / 1048576.0, 1); s3 = "GB"; }
            if (s2.EndsWith(".0")) s2 = s2.Substring(0, s2.Length - 2);
            return s2 + s3;
        }


        /** Removes this window from the window list and disposes of it.
         Returns false if the user cancels the "save changes" dialog. */
        public bool close()
        {
            bool isRunning = running || running2;
            running = running2 = false;
            if (imp == null) return true;
            bool isVirtual = imp.getStackSize()>1 && imp.getStack().isVirtual();
            if (isRunning) IJ.wait(500);
            if (imp==null) return true;
            bool changes = imp.changes;
            Roi roi = imp.getRoi();
            if (roi!=null && (roi is PointRoi) && ((PointRoi) roi).promptBeforeDeleting())
                changes = true;
            if (ij==null || ij.quittingViaMacro() || IJ.getApplet()!=null || Interpreter.isBatchMode() || IJ.MacroRunning() || isVirtual)
                changes = false;
            if (changes) {
                string msg;
                string name = imp.getTitle();
                if (name.Length>22)
                    msg = "Save changes to\n" + "\"" + name + "\"?";
                else
                    msg = "Save changes to \"" + name + "\"?";
                if (imp.isLocked())
                    msg += "\nWARNING: This image is locked.\nProbably, processing is unfinished (slow or still previewing).";
                BringToFront();
                var d = MessageBox.Show(msg, "ImageJ", MessageBoxButtons.YesNoCancel);
                if (d == DialogResult.Cancel)
                    return false;
                else if (d == DialogResult.Yes) {
                    FileSaver fs = new FileSaver(imp);
                    if (!fs.save()) return false;
                }
            }
            closed = true;
            if (WindowManager.getWindowCount() == 0)
            {
                xloc = 0;
                yloc = 0;
            }
            WindowManager.removeWindow(this);
            if (ij != null && ij.quitting())  // this may help avoid thread deadlocks
            return true;
            Rectangle bounds = this.Bounds;
            if (initialLoc != null && !bounds.Equals(initialLoc) && !IJ.isMacro()
            && bounds.Y < screenHeight / 3 && (bounds.Y + bounds.Height) <= screenHeight
            && (bounds.X + bounds.Width) <= screenWidth)
            {
                Prefs.saveLocation(LOC_KEY, new Point(bounds.X, bounds.Y));
                xbase = -1;
            }
            Dispose();
            if (imp != null)
                imp.flush();
            imp = null;
            return true;
        }

        public ImagePlus getImagePlus()
        {
            return imp;
        }

        /** Has this window been closed? */
        public bool isClosed()
        {
            return closed;
        }

        /** This method is called by ImageCanvas.mouseMoved(MouseEvent). 
            @see ij.gui.ImageCanvas#mouseMoved
        */
        public void mouseMoved(int x, int y)
        {
            imp.mouseMoved(x, y);
        }

        private void windowActivated(object? sender, EventArgs e)
        {
            if (IJ.debugMode) IJ.log("windowActivated: "+imp.getTitle());
            // if (IJ.isMacOSX())
            //     setImageJMenuBar(this);
            if (imp==null)
                return;
            ImageK ij = IJ.GetInstance();
            if (ij!=null && !closed && !ij.quitting() && !Interpreter.isBatchMode())
                WindowManager.setCurrentWindow(this);
            Roi roi = imp.getRoi();
            // if (roi!=null && (roi is PointRoi))
            //     PointToolOptions.update();
            // if (imp.isComposite())
            //     Channels.updateChannels();
            imp.setActivated(); // notify ImagePlus that image has been activated
        }

    }
}
